﻿using System;
using System.Collections.Generic;
using System.Text;

namespace NamedBinaryTag.Converters
{
	class PathTokenizer : AbstractTokenSource
	{
		private string _path;
		private int _offset;

		internal PathTokenizer(string path)
		{
			_path = path;
			_offset = 0;
			_aheadTokens = new Queue<Token>(3);
		}

		private Token _lastToken;

		internal override Token LastToken
		{
			get { return _lastToken; }
		}

		private Queue<Token> _aheadTokens;

		internal override Token ReadNext()
		{
			ReadAhead(0);
			_lastToken = _aheadTokens.Dequeue();
			return LastToken;
		}

		internal override Token ReadAhead(int offset)
		{
			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset", "Must be a positive or zero integer.");
			if (_aheadTokens.Count > offset)
				return _aheadTokens.ToArray()[offset];
			return ReadAheadNext(offset - _aheadTokens.Count);
		}

		private Token ReadAheadNext(int offset)
		{
			Token token = null;
			if (_offset > _path.Length)
			{
			}
			else if (_offset == _path.Length)
			{
				token = new Token() { Offset = _offset, Type = Token.TokenType.EndOfString, Value = string.Empty };
				_offset++;
			}
			else
			{
				char c = _path[_offset];
				switch (c)
				{
					case '.':
						token = ReadDot();
						break;
					case '[':
						token = ReadOpenBracket();
						break;
					case ']':
						token = ReadCloseBracket();
						break;
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						token = ReadInteger();
						break;
					default:
						token = ReadText();
						break;
				}
			}
			if (token != null)
				_aheadTokens.Enqueue(token);
			if (offset > 0)
				return ReadAheadNext(offset - 1);
			return token;
		}

		private Token ReadDot()
		{
			int begin = _offset;
			char c = _path[_offset++];
			return new Token { Offset = begin, Type = Token.TokenType.Dot, Value = new String(c, 1) };
		}

		private Token ReadOpenBracket()
		{
			int begin = _offset;
			char c = _path[_offset++];
			return new Token { Offset = begin, Type = Token.TokenType.OpenBracket, Value = new String(c, 1) };
		}

		private Token ReadCloseBracket()
		{
			int begin = _offset;
			char c = _path[_offset++];
			return new Token { Offset = begin, Type = Token.TokenType.CloseBracket, Value = new String(c, 1) };
		}

		private Token ReadInteger()
		{
			int begin = _offset;
			StringBuilder value = new StringBuilder();
			int count = 0;
			char c = _path[begin + count];
			while ((c >= '0') && (c <= '9') && ((begin + count) < _path.Length))
			{
				value.Append(c);
				count++;
				if ((begin + count) >= _path.Length)
					break;
				c = _path[begin + count];
			}
			_offset = begin + count;
			return new Token { Offset = begin, Type = Token.TokenType.Integer, Value = value.ToString() };
		}

		private Token ReadText()
		{
			int begin = _offset;
			StringBuilder value = new StringBuilder();
			int count = 0;
			char c = _path[begin + count];
			if (c == '"')
			{
				count++;
				if ((begin + count) >= _path.Length)
					throw new TokenizeException(string.Format("Unexpected End of String while parsing a Quoted string at {0}.", begin));
				c = _path[begin + count];
				while (c != '"')
				{
					if (c == '\\')
					{
						count++;
						if ((begin + count) >= _path.Length)
							throw new TokenizeException(string.Format("Unexpected End of String while parsing a Quoted string at {0}.", begin));
						c = _path[begin + count];
					}
					value.Append(c);
					count++;
					if ((begin + count) >= _path.Length)
						throw new TokenizeException(string.Format("Unexpected End of String while parsing a Quoted string at {0}.", begin));
					c = _path[begin + count];
				}
			}
			else
			{
				while (((c < '0') || (c > '9')) && (c != '.') && (c != '[') && (c != ']') && ((begin + count) < _path.Length))
				{
					value.Append(c);
					count++;
					if ((begin + count) >= _path.Length)
						break;
					c = _path[begin + count];
				}
			}
			_offset = begin + count;
			return new Token { Offset = begin, Type = Token.TokenType.Text, Value = value.ToString() };
		}
	}
}
